package model

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	"blog2021/configs"
	"blog2021/tools/logger"

	"github.com/PuerkitoBio/goquery"
)

type GatherRequestItem struct {
	Title         string `json:"title"`
	Url           string `json:"url"`
	MainSelector  string `json:"mainSelector"`
	TitleSelector string `json:"titleSelector"`
	UrlSelector   string `json:"urlSelector"`
}

type GatherRequest struct {
	List []GatherRequestItem `json:"list"`
}

type GatherResponseItem struct {
	Index int
	Title string
	Url   string
}

type GatherCache struct {
	t     time.Time
	title string
	items []GatherResponseItem
}

type Gather struct {
	filename string
	request  GatherRequest
	cache    map[string]GatherCache
	mutex    sync.Mutex
}

func (p *Gather) Init() error {
	p.cache = make(map[string]GatherCache)
	p.filename = configs.AppDir("configs/gather.json")
	b, err := ioutil.ReadFile(p.filename)
	if err != nil {
		return err
	}

	err = json.Unmarshal(b, &p.request)
	if err != nil {
		return err
	}

	for _, req := range p.request.List {
		logger.Infof("gather title:%s", req.Title)
	}

	return nil
}

func (p *Gather) Fetch(title string) ([]GatherResponseItem, error) {
	result := []GatherResponseItem{}
	for _, req := range p.request.List {
		if req.Title == title {
			doc, err := goquery.NewDocument(req.Url)
			if err != nil {
				return result, err
			}

			itemCount := 0
			doc.Find(req.MainSelector).Each(func(i int, s *goquery.Selection) {
				itemCount += 1
				t := s.Find(req.TitleSelector)
				u := s.Find(req.UrlSelector)
				if len(req.TitleSelector) == 0 {
					t = s
				}
				if len(req.UrlSelector) == 0 {
					u = s
				}
				titleText := t.Text()
				urlText, ok := u.Attr("href")
				if !ok {
					urlText = u.Text()
				}
				if len(titleText) > 0 && len(urlText) > 0 {
					if strings.Index(urlText, "http") != 0 {
						up, err := url.Parse(req.Url)
						if err == nil {
							if len(up.Scheme) > 0 {
								urlText = up.Scheme + "://" + up.Host + urlText
							} else {
								urlText = up.Host + urlText
							}
						}
					}
					result = append(result, GatherResponseItem{i, titleText, urlText})
				}
			})
			logger.Infof("title:%s, item count:%d", title, itemCount)
			return result, nil
		}
	}
	return result, fmt.Errorf("title not found: %s", title)
}

func (p *Gather) FetchAndUpdate(title string) ([]GatherResponseItem, error) {
	items, err := p.Fetch(title)
	if err == nil && len(items) > 0 {
		p.mutex.Lock()
		defer p.mutex.Unlock()
		p.cache[title] = GatherCache{time.Now(), title, items}
		return items, nil
	}
	if err != nil {
		logger.Errorf("FetchAndUpdate title:%s, error:%v", title, err)
	}
	return nil, err
}

func (p *Gather) FetchAll() {
	for _, req := range p.request.List {
		go p.FetchAndUpdate(req.Title)
	}
}

func (p *Gather) Get(title string) []GatherResponseItem {
	cache, ok := p.cache[title]
	if ok {
		if time.Since(cache.t) > (1 * time.Minute) {
			go p.FetchAndUpdate(title)
		}
		return cache.items
	}

	items, err := p.FetchAndUpdate(title)
	if err != nil {
		return []GatherResponseItem{}
	}
	return items
}

func (p *Gather) List() []string {
	ls := []string{}
	for _, req := range p.request.List {
		ls = append(ls, req.Title)
	}
	return ls
}

func (p *Gather) DefaultTitle() string {
	if len(p.request.List) > 0 {
		return p.request.List[0].Title
	}
	return ""
}

func (p *Gather) ReadConfig() (string, error) {
	b, err := ioutil.ReadFile(p.filename)
	if err != nil {
		return "", err
	}
	return string(b[:]), nil
}

func (p *Gather) WriteConfig(content string) error {
	b := []byte(content)
	var newRequest GatherRequest
	err := json.Unmarshal(b, &newRequest)
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(p.filename, b, os.ModePerm)
	if err != nil {
		return err
	}

	p.request = newRequest
	return nil
}
